package com.hongji.lms5xxcommunicator.grid.volume;

import com.hongji.lms5xxcommunicator.grid.core.RasterState;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * <p>体积计算服务类。</p>
 *
 * <p><b>职责：</b></p>
 * <ul>
 *   <li>基于 {@link RasterState} 的栅格均值/命中数据和 {@link VolumeParams} 的配置计算总体积（单位：m³）。</li>
 *   <li>支持：全量重算、差分增量更新（{@link #addDelta(double, double)}）、指定参数临时计算（{@link #compute(double, boolean)}）。</li>
 *   <li>提供懒计算：首次读取 {@link #get()} 时未就绪则自动重算。</li>
 * </ul>
 *
 * <p><b>单位说明：</b></p>
 * <ul>
 *   <li>内部长度单位由 {@link RasterState} / {@link VolumeParams} 决定（如 mm、cm、m）。</li>
 *   <li>通过 {@link #setMetersPerUnit(double)} 指定“每 1 个内部长度单位等于多少米”。例如内部单位为 mm，则传入 0.001。</li>
 *   <li>体积换算系数为 (metersPerUnit)<sup>3</sup>。</li>
 * </ul>
 *
 * <p><b>用法：</b></p>
 * <ol>
 *   <li>构造本类，传入 {@link RasterState} 与 {@link VolumeParams}。</li>
 *   <li>可选：调用 {@link #setMetersPerUnit(double)} 设置单位比例。</li>
 *   <li>调用 {@link #recomputeAll()} 或直接 {@link #get()} 获得体积（m³）。</li>
 *   <li>若进行了局部网格更新，可通过 {@link #addDelta(double, double)} 进行体积差分维护。</li>
 * </ol>
 *
 * @since 1.0
 */
public class VolumeService {

    /** 日志：中文为主，状态短语可用英文（如 ok） */
    private static final Logger log = LoggerFactory.getLogger(VolumeService.class);

    /** 当前的栅格状态，包含点云数据的均值、高度矩阵等 */
    private final RasterState s;
    /** 体积计算参数（与 RasterState 使用同一“内部单位”） */
    private final VolumeParams p;

    /** 长度单位 → 米 的比例（每 1 个内部单位等于多少米）。默认 1.0 表示内部单位就是米。*/
    private double metersPerUnit = 1.0;

    /** 累积体积（m³） */
    private double runningVolume = 0.0;
    /** 是否已计算完成标记（true 表示 {@code runningVolume} 可用） */
    private boolean ready = false;

    /**
     * <p>构造体积服务。</p>
     *
     * @param s 栅格状态 {@link RasterState}
     * @param p 体积参数 {@link VolumeParams}
     * @throws IllegalArgumentException 当参数为空时
     */
    public VolumeService(RasterState s, VolumeParams p) {
        if (s == null || p == null) {
            throw new IllegalArgumentException("RasterState/VolumeParams 不能为空");
        }
        this.s = s;
        this.p = p;
        log.info("体积服务初始化完成：rows={}, cols={}, dx={}, dy={}, baseline={}, clampNegative={}",
                s.spec.rows, s.spec.cols, s.spec.dx, s.spec.dy, p.getBaseline(), p.isClampNegative());
    }

    /**
     * <p>设置长度单位到米的比例。</p>
     *
     * <p>示例：</p>
     * <ul>
     *   <li>内部单位为 mm → 传入 0.001</li>
     *   <li>内部单位为 cm → 传入 0.01</li>
     * </ul>
     *
     * @param metersPerUnit 每 1 个内部长度单位等于多少米（需 > 0）
     * @return this（链式）
     */
    public VolumeService setMetersPerUnit(double metersPerUnit) {
        if (metersPerUnit <= 0) {
            log.warn("设置单位比例失败：metersPerUnit 必须 > 0，收到={}", metersPerUnit);
            return this;
        }
        if (Double.compare(this.metersPerUnit, metersPerUnit) != 0) {
            this.metersPerUnit = metersPerUnit;
            this.ready = false; // 单位比例变化会影响体积，标记失效
            log.info("单位比例已更新：metersPerUnit={}（下次读取将重算）", this.metersPerUnit);
        }
        return this;
    }

    /**
     * <p>计算单个格子的体积贡献（返回 m³）。</p>
     *
     * <p>逻辑：</p>
     * <ol>
     *   <li>按内部单位计算 dz 与 {@code cellArea}（不改变现有精度/路径）。</li>
     *   <li>整体乘以 (metersPerUnit)^3，把“内部单位³”换算为“m³”。</li>
     * </ol>
     *
     * @param meanZ 该格的均值高程（内部单位）
     * @return 该格对体积的贡献（m³）
     */
    private double cellContribution(double meanZ) {
        double dz = meanZ - p.getBaseline();   // 与基准面的差值（内部单位）
        if (p.isClampNegative() && dz < 0) dz = 0;

        // 体积（内部单位^3）
        double vInternal = dz * s.spec.cellArea(); // cellArea = dx*dy（内部单位^2）

        // 换算为 m³
        double k = metersPerUnit * metersPerUnit * metersPerUnit; // (metersPerUnit)^3
        return vInternal * k;
    }

    /**
     * 全量重算总体积（m³）
     */
    public void recomputeAll() {
        double vM3 = 0.0;  // 直接按 m³ 累加（cellContribution 已做单位换算）
        int cells = 0;
        for (int r = 0; r < s.spec.rows; r++) {
            for (int c = 0; c < s.spec.cols; c++) {
                if (!s.hasData[r][c]) continue;
                double m = s.meanZ[r][c];
                if (!Double.isNaN(m)) {
                    vM3 += cellContribution(m);  // m³
                    cells++;
                }
            }
        }
        runningVolume = vM3;   // 单位：m³
        ready = true;
        log.info("体积全量重算完成：volume={} m³，参与单元格数={}", runningVolume, cells);
    }

    /**
     * <p>获取当前体积（m³）。若未计算过则触发一次重算。</p>
     *
     * @return 当前体积（m³）
     */
    public double get() {
        if (!ready) {
            log.debug("体积未就绪，触发一次全量重算");
            recomputeAll();
        }
        return runningVolume;
    }

    /**
     * <p>临时使用给定参数计算体积（m³）。</p>
     *
     * <p>行为：</p>
     * <ul>
     *   <li>暂时修改 {@link VolumeParams#getBaseline()} 与 {@link VolumeParams#isClampNegative()}，全量计算后恢复。</li>
     *   <li>会更新缓存（与原实现保持一致）。</li>
     * </ul>
     *
     * @param baseline 临时基准面（内部单位）
     * @param clamp    临时是否截断负值
     * @return 本次计算后的体积（m³）
     */
    public double compute(double baseline, boolean clamp) {
        double oldB = p.getBaseline();
        boolean oldC = p.isClampNegative();
        p.setBaseline(baseline);
        p.setClampNegative(clamp);
        log.info("临时计算：baseline={}, clampNegative={}（会更新缓存）", baseline, clamp);
        recomputeAll();                 // runningVolume（m³）
        p.setBaseline(oldB);
        p.setClampNegative(oldC);
        return runningVolume;
    }

    /**
     * <p>增量更新总体积（m³）。</p>
     *
     * @param before 更新前这些格子的体积贡献和（m³），请确保与当前 metersPerUnit 一致
     * @param after  更新后这些格子的体积贡献和（m³）
     */
    public void addDelta(double before, double after) {
        double delta = after - before;
        runningVolume += delta;
        ready = true;
        log.debug("体积差分更新：before={}，after={}，delta={} → runningVolume={}",
                before, after, delta, runningVolume);
    }

    /**
     * <p>计算指定格子的体积贡献和（m³）。</p>
     *
     * <p>键编码：高 32 位为行，低 32 位为列（{@code r << 32 | c}）。</p>
     *
     * @param keys 单元格键集合
     * @return 这些单元格的体积贡献和（m³）
     */
    public double sumContributionForKeys(java.util.Set<Long> keys) {
        if (keys == null || keys.isEmpty()) return 0.0;
        double ssum = 0.0;
        int hit = 0;
        for (long k : keys) {
            int r = (int) (k >> 32), c = (int) (k & 0xffffffffL);
            if (!s.inBounds(r, c) || !s.hasData[r][c]) continue;
            double m = s.meanZ[r][c];
            if (!Double.isNaN(m)) {
                ssum += cellContribution(m);
                hit++;
            }
        }
        log.debug("指定格子体积求和：keys={}，有效格子={}，sum={} m³", keys.size(), hit, ssum);
        return ssum; // m³
    }

    /**
     * <p>使缓存失效（下次 {@link #get()} 会全量重算）。</p>
     */
    public void invalidate() {
        if (ready) {
            ready = false;
            log.debug("体积缓存已失效");
        }
    }
}
